package storage_spec::raw;

BEGIN {
        if(!exists($::ENV{TRUECL_CODE})) {
                die("Missing TRUECL_CODE environment variable.");
        }
        if(!exists($::ENV{TRUECL_LOG})) {
                die("Missing TRUECL_LOG environment variable.");
        }
        if(!exists($::ENV{TRUECL_CFG})) {
                die("Missing TRUECL_CFG environment variable.");
        }
	push @::INC,"$::ENV{TRUECL_CODE}/lib";
}

use clreq;
use cldb;
use clstatdb;
use Sys::Hostname;
use Data::Dumper;
use mnttab;
use Config;
use clutils;

my $df_portable=1;
eval "use Filesys::DfPortable";
if($@) {
	$df_portable=0;
}

my $error_msg=undef;

sub get_error_msg {
	return $error_msg;
}

sub new {
my $proto=shift;
my $class=ref($proto) || $proto;
my $self={};

	$self->{TYPE}="Raw Device Files";
	bless($self,$class);
	return $self;
}

sub replicated {
	return 0;
}


sub type {
my $self=shift;
	return $self->{TYPE};
}

# Can define and use a Single image on just one node
sub min_nodes() {
	return 1;
}

# Can used a single image on any of up to 32 nodes
sub max_nodes() {
	return 32;
}

# Can multiple nodes use the resource at the same time without problems?
sub allow_concurrent() {
	return 1;
}

sub repl_ip() {
	return (1,"NONE");
}

#########################################################################
# The below is called via clreqd - and thus must not use clreqd itself!	#
# This assumes that all file systems and logical volumes in use for the	#
# volume group are closed.
#########################################################################

sub run_before_shutdown_on_current($$;$) {
my ($self,$vgname,$lvname)=@_;

	#################################################################
	# RAW does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return(1,undef);
}

#########################################################################
# The below is called via clreqd - and thus must not use clreqd itself!	#
# So each host will get this called via a storage_spec call for a 	#
# volume group / application.						#
#########################################################################

sub run_before_shutdown_on_non_current($$$;$) {
my ($self,$vgname,$repl_map,$lvname)=@_;
	#################################################################
	# RAW does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return (1,undef);
}

sub run_before_start_on_current{
my ($self,$vgname,$allow_stale,$force,$lvname)=@_;

	#################################################################
	# RAW does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return (1,undef);
}

sub run_after_start_on_current($$;$) {
my ($self,$vgname,$lvname)=@_;

	#################################################################
	# RAW does not require any client activity at the device	#
	# level and so we do nothing here.				#
	#################################################################
	return(1,undef);
}

#########################################################################
# RAW can be used as a shared resource, so we do not need to do 	#
# anything to deactivate it at the LV level.				#
#########################################################################

sub run_before_start_on_non_current($$;$$) {
my ($self,$vgname,$repl_map,$lvname)=@_;

	return(1,undef);
}

#########################################################################
# For an RAW resource we don't need to do anything on the 		#
# non-current nodes after it has been started on current...		#
#########################################################################

sub run_after_start_on_non_current {
my ($self,$vgname,$lvname)=@_;

	return(1,undef);
}

#########################################################################
# Will return (1,undef) - if resources allocated, or (0,errmsg) if not. #
#########################################################################

sub alloc_resources_extras {
	return ();
}

#########################################################################
# This is called on one node and communicates with all the others. The	#
# aim is to allocate resources, though nothing to do for RAW really.	#
# Here the vg is irrelevant and the lvname is the logical name. We	#
# do nothing apart from vischeck which takes the physical mappings	#
# and ensures the devices specified exist.				#
#									#
# vgname=dummy/not relevant						#
# lvname=LOGICAL(hosta=/dev/path,b=/dev/path)				#
#########################################################################

sub alloc_resources($$$$$$;$) {
my ($self,$vgname,$lvname,$appname,$nodelist,$openlocally,%extras)=@_;

	# Check the lvname is in the correct format...
	my ($logical,$physical)=($lvname =~ /^(.*)\((.+)\)$/);
	if(!defined($logical) || !defined($physical) || !length($logical) || !length($physical)) {
		return(0,"LV detail must be format 'LOGICAL(hosta=/dev/path,b=/dev/path)'.");
	}
	my @N=split(/,/,$nodelist);
	#################################################################
	# The physical must define the list of nodes for everything 	#
	# in the node list.						#
	#################################################################
	my %dev_map=();
	{
		my %mapp=();
		$mapp{$_}=1 foreach(@N);
		my @F2=split(/,/,$physical);
		for my $cf2 (@F2) {
			my ($a,$b)=($cf2 =~ /^(.+)=(.+)/);
			if(!defined($a) || !defined($b) || !length($a) || !length($b)) {
				return(0,"Details '$cf2' in logical device info is not in correct format.");
			}
			if(!exists($mapp{$a})) {
				return(0,"Node '$a' is not defined in the node list, but in the lv details.");
			}
			delete $mapp{$a};
			$dev_map{$a}=$b;
		}
		if(%mapp) {
			return(0,"Missing device information for hosts: " . join(",",keys(%mapp)));
		}
	}

	my $clreqdxml=new clreqdxml("$::ENV{TRUECL_CFG}/clreqd.xml");
	if(! defined($clreqdxml)) {
		return (0,"Unable to load Request Daemon configuration info.", "Reason: " . clreqdxml::error());
	}

	my $reqd=new clreq(hosts => [@N], clreqdxml => $clreqdxml, timeout => 5);
	my $results=$reqd->run(cmd => ["echo"]);

	my @OK=(); my @DEAD=();
	for (keys(%$results)) {
		if($results->{$_} =~ /Alive!/) {
			push @OK,$_;
		} else {
			push @DEAD,$_;
		}
	}

	if(@DEAD) {
		my $x=join(",",@DEAD);
		return(0,"Following hosts are not available to allocate resources for: $x");
	}

	#################################################################
	# Since this is raw there is nothing to do really ...,  but	#
	# we generate entries in the storage_raw table...		#
	# columns;							#
	# appname	- application name				#
	# logical	- logical name					#
	# physical	- host=device,...				#
	#################################################################

	my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($cldb)) {
		return(0,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
	}

	my $r=$cldb->exists("table","storage_raw");
	$cldb->disconnect;
	if($r==0) {
		$r="CREATE TABLE storage_raw(appname VARCHAR(50), logical VARCHAR(50), physical VARCHAR(255));";
		#########################################################
		# use reqd to create on all hosts if necessary.		#
		#########################################################
		my $results=$reqd->run(cmd => ["perform_adhoc_sql","SQL",$r,"DBTYPE","config"]);
	}

	#################################################################
	# The table exists, so indicate that only this node has 	#
	# actually tested visibilty of the device - used for checking	#
	# later...							#
	#################################################################

	my $h=hostname;
	$r="INSERT OR REPLACE INTO storage_raw (appname,logical,physical) VALUES ('$appname','$logical','$physical');";
	$results=$reqd->run(cmd => ["perform_adhoc_sql","SQL",$r,"DBTYPE","config"]);
	return(1,undef);
}

#########################################################################
# Will return (1,undef) - if resources removed, or (0,errmsg) if not. #
#########################################################################

sub remove_resources($$$$$$) {
my ($self,$vgname,$lvname,$appname,$nodelist,$openlocally)=@_;

	# Check the lvname is in the correct format...
	my ($logical,$physical)=($lvname =~ /^(.*)\((.+)\)$/);
	if(!defined($logical) || !defined($physical) || !length($logical) || !length($physical)) {
		return(0,"LV detail must be format 'LOGICAL(hosta=/dev/path,b=/dev/path)'.");
	}

	my @N=split(/,/,$nodelist);
	my $clreqdxml=new clreqdxml("$::ENV{TRUECL_CFG}/clreqd.xml");
	if(! defined($clreqdxml)) {
		return (0,"Unable to load Request Daemon configuration info.", "Reason: " . clreqdxml::error());
	}

	my $reqd=new clreq(hosts => [@N], clreqdxml => $clreqdxml, timeout => 5);
	my $results=$reqd->run(cmd => ["echo"]);

	my @OK=(); my @DEAD=();
	for (keys(%$results)) {
		if($results->{$_} =~ /Alive!/) {
			push @OK,$_;
		} else {
			push @DEAD,$_;
		}
	}

	if(@DEAD) {
		my $x=join(",",@DEAD);
		return(0,"Following hosts are not available to remove resources from: $x");
	}

	#################################################################
	# Since this is raw there is nothing to do really ...,  but	#
	# we remove any entries from the storage_raw table, if present.	#
	#################################################################

	my $h=hostname;
	$r="DELETE FROM storage_raw WHERE appname = '$appname' AND logical = '$logical';"; 
	if(defined(&::msg)) {
		::msg("Using the following command to remove storage_raw entry:");
		::msg($r);
	}
	$results=$reqd->run(cmd => ["perform_adhoc_sql","SQL",$r,"DBTYPE","config"]);
	my @F=();
	for (keys(%$results)) {
		if($results->{$_} !~ /^OK/) {
			push @F,$_;
		}
	}
	if(@F) {
		# ::msg(Dumper($results));
		return(0,"Failed to remove entries on hosts: ".join(",",@F));
	}
	return(1,undef);
}

#########################################################################
# The device status can be used to indicate the status of the device on	#
# the specified host. For this storage spec it indicates whether it has	#
# been visisbility checked on the host or not before via the 		#
# storage_raw table information.					#
#									#
# Returns (0,errmsg) - an error has occured.				#
#         (1,undef)  - validated the device is working as expected	#
#		       on the specified host.				#
#########################################################################

sub device_status($$$$$) {
my ($self,$vg,$lv,$appname,$host)=@_;

	return(1,undef);
}

#########################################################################
# The device name on which the device is available on this 		#
# partlcar host. We need lv to be at least the logical bit, and get the	#
# rest from the table...						#
# Returns (0,errormsg,"") - error					#
# Returns (1,$local_lv,"") - details, fsck dev is empty too		#
#########################################################################

sub local_devname($$$$) {
my ($self,$vg,$lv,$appname)=@_;

	my ($logical,$physical);
	if($lv =~ /\(/) {
		($logical,$physical)=($lv =~ /^(.*)\((.+)\)$/);
	} else {
		my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
		if(!defined($cldb)) {
			return(0,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
		}

		my $r=$cldb->get_rows("SELECT * FROM storage_raw WHERE appname = '$appname' AND logical = '$lv';");
		$cldb->disconnect;
		
		if(!defined($r->[0]->{physical})) {
			return(0,"No details defined for logical raw device '$lv' for application '$appname'.");
		}
		$physical=$r->[0]->{physical};
	}
	
	#################################################################
	# So physical contains all physical details, so extract		#
	# information for this host.					#
	#################################################################
	my @F2=split(/,/,$physical);
	my $dev=undef;
	for my $cf2 (@F2) {
		my ($a,$b)=($cf2 =~ /^(.+)=(.+)/);
		if(!defined($a) || !defined($b) || !length($a) || !length($b)) {
			return(0,"Details '$cf2' in logical device info is not in correct format.");
		}
		if($a eq hostname()) {
			$dev=$b; last;
		}
	}
	if(!defined($dev)) {
		return(0,"Missing device information for host:" . hostname());
	}
	return (1,$dev,""); # fsck dev is empty too
}

#########################################################################
# The raw image type can NOT be be out of sync [since all storage	#
# class as local. If you want drbd raw storage, then you simply define	#
# an lv using drbd. Raw is for storage outside of a volume manager.	#
#									#
# Hence we also indicate it is synched.					#
# and hence sync_attempts_supported==0 and hence this routine should	#
# never be called - but is! So always return the details of the raw	#
# volumes, but with a status of 1 for all vols...
#########################################################################

sub sync_status($$$) {
my ($self,$vgname,$appname)=@_;
my @R=();

	#################################################################
	# Get a list of all NFS items since vgname will be nfs...	#
	#################################################################
	my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($cldb)) {
		return(0,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
	}

	#################################################################
	# Use config database to get all nfs stuff for application.	#
	#################################################################

	my $r=$cldb->get_rows("SELECT * FROM storage_raw WHERE appname = '$appname';");
	$cldb->disconnect;
	
	for my $r (@$r) {
		next if !defined($r->{logical});
		push @R,[$r->{logical},1,0];
	}
	if(@R) {
		return (1,@R);
	}
	return 1;
}

#########################################################################
# Raw storage expects all storage to be treated as shared raw, so no	#
# sync requirements.							#
#########################################################################

sub sync_attempts_supported($) {
my $self=$_[0];

	return 0;
}

#########################################################################
# The below should never be called for this storage type and so		#
# if it is always indicate that the request is successful!!		#
#########################################################################

sub do_sync_attempt($$$$$) {
my ($self,$vgname,$lvname,$appname,$attempt)=@_;

	return (2,undef);
}

#########################################################################
# Indicates that the device in question is currently synched. Can be a 	#
# no-op if necessary, or can update local info - in this case the	#
# storage_drbd1 table in the config database.				#
# (0,errmsg) - error occurred						#
# (1,undef) - OK							#
#########################################################################

sub registered_synched($$$$) {
my ($self,$vgname,$lvname,$appname)=@_;

	return(1,undef);
}

sub registered_unsynched($$$$) {

	return(1,undef);
}

#########################################################################
# Resizing is simple for the raw client - it can't be done!		#
#########################################################################

sub resize_lv($$$$$$$) {
my  ($self,$vg,$lv,$appname,$running,$nodelist,$oldsize,$newsize)=@_;

	return(99,"RAW does not support client-side device resize.");
}

#################################################################################
# When we validate we check that devices specified exist on all nodes. 		#
# We also read the contents of the devices and get the values to ensure they	#
# return the same information - otherwise might indicate different disks!!	#
#################################################################################

sub validate {
my ($self,$application,$vg,$lv,$nlist,$running_on_node)=@_;

	my $hostname=hostname();
	#########################################################################
	# We do each device in turn ... using the diskhb_rawread routine.	#
	#########################################################################

	my ($logical_dev)=($lv =~ /^(.*)\(.*\)/);
	if(!defined($logical_dev)) {
		return(1,"Device lv setting of '$lv' is not suitable for raw storage.");
	}
	my $cldb=new cldb("$::ENV{TRUECL_CFG}/config.db");
	if(!defined($cldb)) {
		return(1,"Unable to connect to 'config' database.\nReason: " . (cldb::error)[1]);
	}
	
	my $r=$cldb->get_rows("SELECT * FROM storage_raw WHERE appname = '$application';");
	$cldb->disconnect;
	
	my $check_logical=undef;
	my $check_physical=undef;
	for my $r (@$r) {
		next if !defined($r->{logical});
		next if $r->{logical} ne $logical_dev;
		$check_logical=$r->{logical};
		$check_physical=$r->{physical};
	}
	if(!defined($check_physical)) {
		return(1,"Details for raw device '$logical_dev' not found.");
	}
	# make use of the "diskhb_blksize devname ZZZZ" to get block size..
	my $blksize=512; # sensible default, though iscsi uses 4096 often now...
	my $raw_device=undef;
	my @N=split(/,/,$check_physical);
	for my $cdet (@N) {
		my ($a,$b)=split(/=/,$cdet);
		next if $a ne $hostname;
		$raw_device=$b;
	}
	if(!defined($raw_device)) {
		return(1,"Unable to find device details for host '$hostname'.");
	}
	my $clreqdxml=new clreqdxml("$::ENV{TRUECL_CFG}/clreqd.xml");
	my $reqd=new clreq(hosts => [$hostname], clreqdxml => $clreqdxml, timeout => 5);
	my $results=$reqd->run(cmd => ["diskhb_blksize","DEVNAME",$raw_device]);
	if(!defined($results->{$hostname})) {
		return(1,"No results returned from diskhb_blksize query on '$hostname'.");
	}
	if($results->{$hostname} !~ /^OK\s+/) {
		return(1,$results->{$hostname});
	}
	($blksize)=($results->{$hostname} =~ /^OK\s+(\d+)/);
	{
		my @F2=split(/,/,$check_physical);
		my $dev=undef;
		my $cval="";
		my $cdev="";
		my $chost="";
		for my $cf2 (@F2) {
			my ($a,$b)=($cf2 =~ /^(.+)=(.+)/);
			if(!defined($a) || !defined($b) || !length($a) || !length($b)) {
				return(1,"Details '$cf2' in logical device info for '$logical' is not in correct format.");
			}
			my $reqd=new clreq(hosts => [$a], clreqdxml => $clreqdxml, timeout => 5);
			my $results=$reqd->run(cmd => ["fsobj_type","FSOBJ",$b]);
			if(!defined($results->{$a}) || $results->{$a} !~ /^OK/) {
				return(1,"Failed to run request 'fsobj_type' on host '$a', device '$b'.");
			}
			my @FFF=split(/\s+/,$results->{$a});
			if($FFF[1] eq "NOT_EXIST") {
				return(1,"Device file '$b' on host '$a' does not exist.");
			}
			if($FFF[1] ne "CHAR" && $^O ne "linux") {
				return(1,"File system object '$b' on host '$a' is not a character device.");
			}
			my $offset=10*$blksize;
			$results=$reqd->run(cmd => ["diskhb_rawread","DEVNAME",$b,"OFFSET",$offset,"LENGTH",$blksize]);
			$reqd=undef;
			if(!defined($results->{$a}) || $results->{$a} !~ /^OK/) {
				return(1,"Failed to read $blksize bytes from device '$b' at offset $offset.");
			}
			if($cval eq "") {
				$cval=$results->{$a};
				$cdev=$b;
				$chost=$a;
			}
			if($cval ne $results->{$a}) {
				return(1,"Results for device '$b' on '$a' differ from device '$cdev' on '$chost'.");
			}
		}
	}
	return(0,undef);
}

sub min_sync_interval {
	return 20;
}

sub sync_boundaries {
	return (-1,-1);
}

#################################################################
# Return (2,undef) indicating operation is not supported.	#
#################################################################

sub set_sync_rate($$$$$;$) {
	return(2,undef);
}

#################################################################
# We assume that all OS's support shared raw devices.		#
#################################################################

sub supported_os {
	return(1);
}

#################################################################
# raw has no pre-checks for the node to validate/import on.	#
#################################################################

sub check_use_state($$$$$) {
my ($self,$vgname,$appname,$action,$nodelist)=@_;

	return(0,undef);
}

#################################################################
# Live migration routines available [this is because raw can 	#
# be written everywhere - though expect char devices on 	#
# non-linux platforms.						#
#################################################################

sub support_lmigrate($) {
	return 1;
}

sub onode_premigrate($$$$) {
	return(0,undef);
}

sub nnode_premigrate($$$$) {
	return(0,undef);
}

sub onode_postmigrate($$$$) {
	return(0,undef);
}

sub nnode_postmigrate($$$$) {
	return(0,undef);
}

# Return a single line representation of a logical volume name 
# Here we take the logical(a,b,c) and just return logical
# Here we take the logical=(a,b,c) and just return logical

sub strfmt_logical_short($$) {
my $self=shift;
my $a=shift;

	my ($logical,$physical)=("","");
	($logical,$physical)=($a =~ /^(.*)\((.+)\)$/);
	if(!defined($logical)) {
		$logical="";
	}
	return $logical;
}

# Return (possibly) multiple line representation of a logical volume name 
# For raw logical(host=dev,host=dev,...) 
#
#	logical
#		host=dev
#		...

sub strfmt_logical_long($$) {
my $self=shift;
my $a=shift;
	
	my ($logical,$physical)=("","");
	($logical,$physical)=($a =~ /^(.*)\((.+)\)$/);
	if(!defined($logical) || !defined($physical)) {
		return undef;
	}
	my @C=split(/,/,$physical);
	my @D=($logical);
	push @D,"  $_" foreach(@C);
	return @D;
}

1;
